#ifndef UHM_MAT_BASE_MAT_HXX_
#define UHM_MAT_BASE_MAT_HXX_

namespace uhm {

  // Base object do not include template parameters.
  class Mat_Base_  { 
  private:
  protected:
    Int_ _type, _uplo, _sym, _m, _n;

  public:

    enum { INT = 10, 
           FLOAT,
           DOUBLE,
           FCMPLX,
           DCMPLX } Type_;

    enum { LEFT = 100,
           RIGHT } Side_;

    enum { TRANSPOSE =200,
           NO_TRANSPOSE,
           CONJ_TRANSPOSE } Trans_;

    enum { ZERO_DIAG = 300,
           NONUNIT_DIAG,
           UNIT_DIAG } Diag_;

    enum { FULL =400,
           UPPER_TRIANGULAR,
           LOWER_TRIANGULAR } Uplo_;

    enum { SYM = 500,
           UNSYM } Sym_;

    enum { CONJUGATE = 600,
           NO_CONJUGATE } Conjugate_;

    enum { INVERSE = 700,
           NO_INVERSE } Inverse_;

    Mat_Base_() : _type(DOUBLE),
                  _uplo(FULL),
                  _sym(UNSYM),
                  _m(0),
                  _n(0) { }

    Mat_Base_(Int_ type, Int_ uplo, Int_ sym, Int_ m, Int_ n) : _type(type),
                                                                _uplo(uplo),
                                                                _sym(sym),
                                                                _m(m),
                                                                _n(n) { }

    Mat_Base_(Int_ type, Int_ m, Int_ n) : _type(type),
                                           _uplo(FULL),
                                           _sym(UNSYM),
                                           _m(m),
                                           _n(n) { }
    
    Mat_Base_(const Mat_Base_ &b) : _type(b._type),
                                    _uplo(b._uplo),
                                    _sym(b._sym),
                                    _m(b._m),
                                    _n(b._n) { }
    
    virtual ~Mat_Base_() { }

    Mat_Base_& operator=(const Mat_Base_ &b) {
      this->_type = b._type;
      this->_uplo = b._uplo;
      this->_sym  = b._sym;
      this->_m    = b._m;
      this->_n    = b._n;

      return (*this);
    }

    void set(Int_ type, Int_ uplo, Int_ sym, Int_ m, Int_ n) { 
      _type = type; _uplo = uplo; _sym = sym; _m = m; _n = n;
    }

    // Overloaded by derived classes
    void set(Int_ type, Int_ m, Int_ n) {
      _type = type; _m = m; _n = n; 
      _uplo = FULL; _sym = UNSYM;
    }

    Int_ get_type() const { return _type; }
    Int_ get_uplo() const { return _uplo; }
    Int_ get_sym() const { return _sym; }

    Int_ get_m() const { return _m; }
    Int_ get_n() const { return _n; }

    Bool_ is_complex() const { return (_type == DCMPLX || _type == FCMPLX); }
    Bool_ is_float() const { return (_type == FLOAT || _type == FCMPLX); }

    virtual Int_ get_datatype_size() const {
      Int_ r_val =0;
      switch (_type) {
      case INT    : r_val = sizeof(Int_); break;
      case FLOAT  : r_val = sizeof(Float_); break;
      case DOUBLE : r_val = sizeof(Double_); break;
      case FCMPLX : r_val = sizeof(fCmplx_); break;
      case DCMPLX : r_val = sizeof(dCmplx_); break;
      }
      return r_val;
    }

    virtual Double_ get_storage() {
      return get_buffer(this->get_datatype_size(), _m, _n);
    }

    // tracing storage...
    virtual void create_flat_buffer() { 
      storage_counter_in(this->get_storage());
    }
    virtual void free_flat_buffer() {
      storage_counter_out(this->get_storage());
    }
  };

  inline Bool_ is_complex(Int_ type)  { return (type == Mat_Base_::DCMPLX || 
                                                type == Mat_Base_::FCMPLX); }
  inline Bool_ is_float(Int_ type)  { return (type == Mat_Base_::FLOAT || 
                                              type == Mat_Base_::FCMPLX); }

  // Id is used for the matrix cache index.
  // Flat and Hier is for task parallelism.
  template<class F_, class H_>
  class Mat_ : public Mat_Base_, 
               public Disp_ {
  private:
  protected:
    F_ _flat;
    H_ _hier;

  public:
    Mat_() : Mat_Base_() { }

    Mat_(Int_ type, Int_ uplo, Int_ sym, 
         Int_ m, Int_ n) : Mat_Base_(type, uplo, sym, m, n) { }
    
    Mat_(Int_ type, Int_ m, Int_ n) : Mat_Base_(type, m, n) { }
    
    Mat_(const Mat_ &b) : _flat(b._flat), 
                          _hier(b._hier), 
                          Mat_Base_(b) { }

    virtual ~Mat_() { free_all(); }

    void create_flat_without_buffer();
    void create_flat_buffer();

    void* get_flat_buffer(Int_ offn=0) const;

    // should check whether it is already null
    void free_flat_buffer();

    // external buffer that I don't control..
    void attach_flat_buffer(Int_ cs, Int_ rs, void *buffer);
    void free_flat_without_buffer();

    void create_hier(Int_ bm, Int_ bn);
    void free_hier();

    void free_all() { 
      this->free_hier();
      this->free_flat_buffer();
      this->free_flat_without_buffer();
    }

    void set_zero();
    void set_identity();

    void hermitianize();
    void transpose();

    Bool_ is_flat_buffer_null();

    F_ get_flat() const { return _flat; }
    H_ get_hier() const { return _hier; }

    F_& flat() { return _flat; }
    H_& hier() { return _hier; }

    // default computation unit
    H_& operator ~() { return _hier; }

    Mat_& operator=(const Mat_ &b) {

      Mat_Base_::operator=(b);

      this->_flat = b._flat;
      this->_hier = b._hier;

      return (*this);
    }

    virtual std::ostream& content(std::ostream& os) const  {
      this->Disp_::content(os);
      os << "- Mat General Info, "
         << " Type " << this->_type
         << " Mat Uplo " << this->_uplo
         << " Mat Sym " << this->_sym
         << " Dim " << this->_m << ", " << this->_n << std::endl;
      os << "- Flat -" << std::endl;
      os << this->_flat << std::endl;
      os << "- Hier -" << std::endl;
      os << this->_hier << std::endl;
      
      return (os << std::endl);
    }
  };
}


#endif
